package cn.edu.seu.ssny.service.impl;


import cn.edu.seu.ssny.common.BusinessCollections;
import cn.edu.seu.ssny.common.Constants;
import cn.edu.seu.ssny.enums.ResultCodeEnum;
import cn.edu.seu.ssny.common.utils.DateUtils;
import cn.edu.seu.ssny.dao.IDeviceDataStdDao;
import cn.edu.seu.ssny.dao.support.IBaseDao;
import cn.edu.seu.ssny.entity.*;
import cn.edu.seu.ssny.exception.SsnyException;
import cn.edu.seu.ssny.service.*;
import cn.edu.seu.ssny.service.support.impl.BaseServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

import static cn.edu.seu.ssny.common.utils.DateUtils.addTime;
import static cn.edu.seu.ssny.common.utils.DateUtils.isSameTime;

/**
 * 设备数据表
 */
@Service("deviceDataStdService")
public class DeviceDataStdServiceImpl extends BaseServiceImpl<DeviceDataStd, Integer> implements IDeviceDataStdService {

    @Autowired
    private IDeviceDataStdDao deviceDataStdDao;

    @Autowired
    private IDeviceDataService deviceDataService;

    @Autowired
    private IDeviceDataStdService deviceDataStdService;

    @Autowired
    private IDeviceService deviceService;

    @Autowired
    private IEquipmentService equipmentService;

    @Autowired
    private IEquipmentTypeInProjectService equipmentTypeInProjectService;

    @Autowired
    private IProjectService projectService;

    @Override
    public IBaseDao<DeviceDataStd, Integer> getBaseDao() {
        return this.deviceDataStdDao;
    }

    //供冷模式
    private static final int MODE_COLD = 0;
    //供暖模式
    private static final int MODE_HOT = 1;


    @Override
    public Optional<DeviceDataStd> findFirstByDeviceOrderByCreateTimeDesc(Device device) {
        return deviceDataStdDao.findFirstByDeviceOrderByCreateTimeDesc(device);
    }

    @Override
    public Optional<DeviceDataStd> findByDeviceAndCreateTime(Device device, Date createTime) {
        return deviceDataStdDao.findByDeviceAndCreateTime(device, createTime);
    }

    @Override
    public List<DeviceDataStd> findAllByDeviceAndCreateTimeBetween(Device device, Date start, Date end) {
        return deviceDataStdDao.findAllByDeviceAndCreateTimeBetween(device, start, end);
    }

    @Override
    public List<DeviceDataStd> findAllByDeviceAndCreateTimeBetweenOrderByCreateTimeAsc(Device device, Date start, Date end) {
        return deviceDataStdDao.findAllByDeviceAndCreateTimeBetweenOrderByCreateTimeAsc(device, start, end);
    }

    @Override
    public void modifyDate(DeviceDataStd deviceDataStd, DeviceData deviceData) {
        DeviceDataStd dbDeviceDataStd = find(deviceDataStd.getId());
        dbDeviceDataStd.setDeviceData(deviceData);
        double data = deviceData.getData();
        if (deviceData.getIsModified()) {
            data = deviceData.getModifiedData();
        }
        dbDeviceDataStd.setData(data);
        update(dbDeviceDataStd);
    }

    @Override
    public DeviceDataStd update(DeviceDataStd entity) {
        // 需要同时更新 tb_deivce_data_std 和 tb_device_data_std_{year,month,day}
        deviceDataStdDao.update(entity.getData(), entity.getDeviceData().getId(), entity.getDevice().getId(),
                entity.getId());
        deviceDataStdDao.updateYear(entity.getData(), entity.getDeviceData().getId(), entity.getDevice().getId(),
                entity.getId());
        deviceDataStdDao.updateMonth(entity.getData(), entity.getDeviceData().getId(), entity.getDevice().getId(),
                entity.getId());
        deviceDataStdDao.updateDay(entity.getData(), entity.getDeviceData().getId(), entity.getDevice().getId(),
                entity.getId());
        deviceDataStdDao.updateHour(entity.getData(), entity.getDeviceData().getId(), entity.getDevice().getId(),
                entity.getId());
        return entity;
    }

    @Override
    public void create(DeviceData deviceData, Date createTime) {
        DeviceDataStd deviceDataStd = new DeviceDataStd();
        deviceDataStd.setCreateTime(createTime);
        deviceDataStd.setDevice(deviceData.getDevice());
        deviceDataStd.setData(deviceData.getData());
        deviceDataStd.setDeviceData(deviceData);
        saveAndFlush(deviceDataStd);
    }

    @Override
    public DeviceDataStd saveAndFlush(DeviceDataStd entity) {
        //需要同时添加 tb_deivce_data_std 和 tb_device_data_std_{year,month,day}
        deviceDataStdDao.insert(entity.getCreateTime(), entity.getData(), entity.getDevice().getId(), entity.getDeviceData().getId());
        deviceDataStdDao.insertYear(entity.getCreateTime(), entity.getData(), entity.getDevice().getId(), entity.getDeviceData().getId());
        deviceDataStdDao.insertMonth(entity.getCreateTime(), entity.getData(), entity.getDevice().getId(), entity.getDeviceData().getId());
        deviceDataStdDao.insertDay(entity.getCreateTime(), entity.getData(), entity.getDevice().getId(), entity.getDeviceData().getId());
        deviceDataStdDao.insertHour(entity.getCreateTime(), entity.getData(), entity.getDevice().getId(), entity.getDeviceData().getId());
        return entity;
    }

    /**
     * 流程
     * 1. 取到数据之后不需要判断开启状态
     * 2. 获得deviceData的时间
     * 3. 利用时间工具取出对应的小时，再根据device的时间间隔在对应的小时上加上对应的分钟，比较哪一个时间点更接近deviceData的时间
     * 4. 判断数据库中是否已经有对应时间和deviceId的deviceDataStd，如果有则更新数据，没有则创造新的入库
     */
    @Override
    public void newDataProcess(DeviceData deviceData) {
        Date stdTime = deviceDataService.getRoundTime(deviceData);
        Optional<DeviceDataStd> deviceDataStdOption = findByDeviceAndCreateTime(deviceData.getDevice(), stdTime);
        if (deviceDataStdOption.isPresent()) {
            DeviceDataStd deviceDataStd = deviceDataStdOption.get();
            modifyDate(deviceDataStd, deviceData);
        } else {
            create(deviceData, stdTime);
        }
    }

    @Override
    public void deleteFromYear() {
        deviceDataStdDao.deleteFromYear();
    }

    @Override
    public void deleteFromMonth() {
        deviceDataStdDao.deleteFromMonth();
    }

    @Override
    public void deleteFromDay(int interval) {
        deviceDataStdDao.deleteFromDay(interval);
    }

    @Override
    public void deleteFromHour(int interval) {
        deviceDataStdDao.deleteFromHour(interval);
    }

    @Override
    public List<DeviceDataStd> findAllByDeviceIdsInAndCreateTimeBetweenOrderByCreateTimeASC(List<Integer> deviceIds, int timeMode, Date startDate, Date endDate) {
        List<DeviceDataStd> list = new ArrayList<>();
        if (deviceIds == null || deviceIds.size() == 0) {
            return list;
        }
        if (timeMode == DateUtils.MODE_HOUR) {
            list = deviceDataStdDao.findAllByDeviceInAndHourOrderByCreateTimeASC(deviceIds, startDate, endDate);
        } else if (timeMode == DateUtils.MODE_DAY) {
            list = deviceDataStdDao.findAllByDeviceInAndDayOrderByCreateTimeASC(deviceIds, startDate, endDate);
        } else if (timeMode == DateUtils.MODE_MONTH) {
            list = deviceDataStdDao.findAllByDeviceInAndMonthOrderByCreateTimeASC(deviceIds, startDate, endDate);
        } else if (timeMode == DateUtils.MODE_YEAR) {
            list = deviceDataStdDao.findAllByDeviceInAndYearOrderByCreateTimeASC(deviceIds, startDate, endDate);
        }
        return list;
    }

    @Override
    public List<DeviceDataStd> findAllByDeviceInAndCreateTimeBetweenOrderByCreateTimeASC(List<Device> devices, int timeMode, Date startDate, Date endDate) {
        List<DeviceDataStd> list = null;
        if (devices == null || devices.size() <= 0) {
            return new ArrayList<>();
        }
        List<Integer> deviceIds = new ArrayList<>();
        devices.forEach(device -> {
            deviceIds.add(device.getId());
        });
        return this.findAllByDeviceIdsInAndCreateTimeBetweenOrderByCreateTimeASC(deviceIds, timeMode, startDate, endDate);
    }

    @Override
    public Double findEquipmentDeviceDataStd(Equipment equipment, String deviceType, Date createTime) {
        Device device = equipmentService.getEquipmentDeviceByType(equipment, deviceType);
        double data = Double.NaN;
        if (device != null) {
            Optional<DeviceDataStd> dataStd = findByDeviceAndCreateTime(device, createTime);
            data = dataStd.map(DeviceDataStd::getData).orElse(Double.NaN);
        }
        return data;
    }


    /**
     todo：
     新数据传入：检查device为特定类型，获得equipment，检查equipment的制冷制热状态（如果有具体时间）
     获得对应的对偶制冷/制热device，存入数据
     cop，直接用制冷制热device就可以了
     先写出一套cop计算逻辑
     *
     * 查出所有的cop、所有的能耗usage
     */

    /**
     * 基础，从device data std中获取递增数据差值
     *
     * @param device
     * @param timeList
     * @param mode
     * @return
     */
    public List<Double> getUsageByAddedDevice(Device device, List<Date> timeList, String mode) {
        List<Double> dataList = new ArrayList<>();
        Date start = timeList.get(0);
        Date end = timeList.get(timeList.size() - 1);
        Date pre = new Date(start.getTime());
        Date curr = addTime(pre, false, mode, 5);
        Optional<DeviceDataStd> preData = deviceDataStdService.findByDeviceAndCreateTime(device, pre);
        while (!isSameTime(pre, end)) {
            Optional<DeviceDataStd> currData = deviceDataStdService.findByDeviceAndCreateTime(device, curr);
            if (currData.isPresent() && preData.isPresent()) {
                dataList.add(currData.get().getData() - preData.get().getData());
            } else {
                dataList.add(0.0);
            }
            preData = currData;
            pre.setTime(curr.getTime());
            curr = addTime(curr, false, mode, 5);
        }
        return dataList;
    }

    /**
     * 基础，从device data std中获取递增数据差值
     *
     * @param device
     * @param timeList
     * @param mode
     * @return
     */
    public List<Double> getValueByDevice(Device device, List<Date> timeList, String mode) {
        List<Double> dataList = new ArrayList<>();
        Date curr = timeList.get(0);
        Date end = timeList.get(timeList.size() - 1);
        end = addTime(end, false, mode, 5);
        while (!isSameTime(curr, end)) {
            Optional<DeviceDataStd> currData = deviceDataStdService.findByDeviceAndCreateTime(device, curr);
            if (currData.isPresent()) {
                dataList.add(currData.get().getData());
            } else {
                dataList.add(0.0);
            }
            curr = addTime(curr, false, mode, 5);
        }
        return dataList;
    }

    /**
     * 查数据库获取数据list
     */

    private Double getDiffData(Device device, Date start, Date end) {
        Optional<DeviceDataStd> startData = deviceDataStdService.findByDeviceAndCreateTime(device, start);
        Optional<DeviceDataStd> endData = deviceDataStdService.findByDeviceAndCreateTime(device, end);
        if (!startData.isPresent() || !endData.isPresent())
            return 0.0;
        return endData.get().getData() - startData.get().getData();
    }

    public Map<String, List<Double>> getQPMap(List<Date> timeList, Equipment equipment, String mode) {
        Map<String, List<Double>> qpMap = new HashMap<>();
        Optional<Device> cqDevice;
        Optional<Device> hqDevice;
        if (equipment.getType().getTypeName().getName().equals("地源热泵")) {
            cqDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%负荷侧制冷量%");
            hqDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%负荷侧制热量%");
        } else {
            cqDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%制冷量%");
            hqDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%制热量%");
        }
        cqDevice.ifPresent(device -> qpMap.put("cqList", getUsageByAddedDevice(device, timeList, mode)));
        hqDevice.ifPresent(device -> qpMap.put("hqList", getUsageByAddedDevice(device, timeList, mode)));
        Optional<Device> pDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%能耗%");
        pDevice.ifPresent(device -> qpMap.put("pList", getUsageByAddedDevice(device, timeList, mode)));
        return qpMap;
    }

    public Map<String, List<Double>> getPumpMap(List<Date> timeList, Equipment equipment, String mode) {
        Map<String, List<Double>> qpMap = new HashMap<>();
        List<Date> rawTimeList = timeList.subList(1, timeList.size());
        Optional<Device> mDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%总流量%");
        mDevice.ifPresent(device -> qpMap.put("mList", getValueByDevice(device, rawTimeList, mode)));
        Optional<Device> poutDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%出口压力%");
        poutDevice.ifPresent(device -> qpMap.put("poutList", getValueByDevice(device, rawTimeList, mode)));
        Optional<Device> pinDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%进口压力%");
        pinDevice.ifPresent(device -> qpMap.put("pinList", getValueByDevice(device, rawTimeList, mode)));
        Optional<Device> nDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%功率%");
        nDevice.ifPresent(device -> qpMap.put("nList", getValueByDevice(device, rawTimeList, mode)));
        return qpMap;
    }

    private Map<String, List<Double>> getBalanceTempMap(List<Date> timeList, Equipment equipment, String mode) {
        Map<String, List<Double>> tempMap = new HashMap<>();
        List<Date> rawTimeList = timeList.subList(1, timeList.size());
        Optional<Device> tempDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%土壤温度%");
        tempDevice.ifPresent(device -> tempMap.put("土壤温度", getValueByDevice(device, rawTimeList, mode)));
        return tempMap;
    }

    private Map<String, List<Double>> getBuriedPipeValueMap(List<Date> timeList, Equipment equipment, String mode) {
        Map<String, List<Double>> map = new HashMap<>();
        List<Date> rawTimeList = timeList.subList(1, timeList.size());
        Optional<Device> mDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%总流量%");
        mDevice.ifPresent(device -> map.put("mList", getValueByDevice(device, rawTimeList, mode)));
        Optional<Device> toutDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%出水温度%");
        toutDevice.ifPresent(device -> map.put("poutList", getValueByDevice(device, rawTimeList, mode)));
        Optional<Device> tinDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%进水温度%");
        tinDevice.ifPresent(device -> map.put("pinList", getValueByDevice(device, rawTimeList, mode)));
        return map;
    }

    /**
     * 计算过程
     */
    private Map<String, List<Double>> calCopCHAll(Map<String, List<List<Double>>> listMap) {
        Map<String, List<Double>> copAllList = new HashMap<>();
        collectList("制冷量", copAllList, listMap);
        collectList("制热量", copAllList, listMap);
        collectList("能耗", copAllList, listMap);
        return copAllList;
    }

    private void collectList(String type, Map<String, List<Double>> copAllList, Map<String, List<List<Double>>> listMap) {
        List<Double> cList = new ArrayList<Double>();
        if (!listMap.containsKey(type))
            return;
        for (List<Double> list : listMap.get(type)) {
            for (int i = 0; i < list.size(); i++) {
                if (i < cList.size()) {
                    if (list.get(i).isNaN())
                        cList.set(i, 0.0);
                    else
                        cList.set(i, cList.get(i) + list.get(i));
                } else {
                    if (list.get(i).isNaN())
                        cList.add(0.0);
                    else
                        cList.add(list.get(i));
                }
            }
        }
        copAllList.put(type, cList);
    }

    private void calCopCH(Map<String, List<Double>> copMap, Map<String, List<Double>> pqMap, String typeName) {
        List<Double> qList = pqMap.get("qList");
        List<Double> pList = pqMap.get("pList");
        if (qList == null || pList == null) {
            return;
        }
        if (qList.size() == 0 || qList.size() != pList.size())
            return;
        List<Double> cop = new ArrayList<>();
        for (int i = 0; i < pList.size(); i++) {
            if (qList.get(i).isNaN() || pList.get(i).isNaN())
                cop.add(0.0);
            else {
                double data = qList.get(i) / pList.get(i);
                cop.add(Double.isNaN(data) ? 0 : data);
            }
        }
        copMap.put(typeName, cop);
    }

    private void calCopCHAll(String status, Map<String, List<Double>> copMap, List<Double> qList, List<Double> pList) {
        if (qList == null || pList == null) {
            return;
        }
        if (qList.size() == 0 || qList.size() != pList.size())
            return;
        List<Double> cop = new ArrayList<>();
        for (int i = 0; i < pList.size(); i++) {
            if (qList.get(i).isNaN() || pList.get(i).isNaN())
                cop.add(0.0);
            else {
                double data = qList.get(i) / pList.get(i);
                cop.add(Double.isNaN(data) ? 0 : data);
            }
        }
        copMap.put(status, cop);
    }


    private void calPumpN(Map<String, List<Double>> pumpNMap, Map<String, List<Double>> pumpMap, String typeName) {
        List<Double> mList = pumpMap.getOrDefault("mList", null);
        List<Double> poutList = pumpMap.getOrDefault("poutList", null);
        List<Double> pinList = pumpMap.getOrDefault("pinList", null);
        List<Double> nList = pumpMap.getOrDefault("nList", null);
        if (mList == null || pinList == null || poutList == null || nList == null)
            return;
        int len = mList.size();
        if (len == 0 || len != pinList.size() || len != poutList.size() || len != nList.size())
            return;
        List<Double> pumpN = new ArrayList<>();
        for (int i = 0; i < nList.size(); i++) {
            if (mList.get(i).isNaN() || poutList.get(i).isNaN() || pinList.get(i).isNaN() || nList.get(i).isNaN())
                pumpN.add(0.0);
            else {
                double data = 2.777 * mList.get(i) * (poutList.get(i) - pinList.get(i)) / nList.get(i);
                pumpN.add(Double.isNaN(data) ? 0 : data);
            }
        }
        pumpNMap.put(typeName, pumpN);
    }

    private void calBuriedPipe(Map<String, List<Double>> buriedPipeCaledMap, Map<String, List<Double>> buriedPipeMap, Equipment equipment) {
        List<Double> mList = buriedPipeMap.getOrDefault("mList", null);
        List<Double> poutList = buriedPipeMap.getOrDefault("poutList", null);
        List<Double> pinList = buriedPipeMap.getOrDefault("pinList", null);

        if (mList == null || pinList == null || poutList == null)
            return;
        int len = mList.size();
        if (len == 0 || len != pinList.size() || len != poutList.size())
            return;
        List<Double> list = new ArrayList<>();
        for (int i = 0; i < mList.size(); i++) {
            if (mList.get(i).isNaN() || poutList.get(i).isNaN() || pinList.get(i).isNaN())
                list.add(0.0);
            else {
                double data = 0.0125 * mList.get(i) * Math.abs(poutList.get(i) - pinList.get(i));
                list.add(Double.isNaN(data) ? 0 : data);
            }
        }
        buriedPipeCaledMap.put(equipment.getName(), list);
    }

    private void addByType(List<Double> pList, Map<String, List<Double>> pqMap, String key) {
        if (pList != null) {
            if (pqMap.containsKey(key)) {
                List<Double> list = pqMap.get(key);
                for (int i = 0; i < list.size(); i++) {
                    if (list.get(i).isNaN()) {
                        list.set(i, pList.get(i));
                    } else {
                        double data = pList.get(i).isNaN() ? 0 : pList.get(i);
                        list.set(i, list.get(i) + data);
                    }
                }
            } else {
                pqMap.put(key, pList);
            }
        }
    }

    /**
     * 调用获取计算好的Map
     */
    @Override
    public Map<String, List<Double>> getCopCH(List<Date> timeList, Project project, String mode, String CH) {
        Map<String, List<Double>> copMap = new HashMap<>();
        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if (BusinessCollections.copTypeSet.contains(type.getTypeName().getName())) {
                Map<String, List<Double>> pqMap = new HashMap<>();
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    Map<String, List<Double>> map = getQPMap(timeList, equipment, mode);
                    List<Double> cqList = map.getOrDefault("cqList", null);
                    List<Double> hqList = map.getOrDefault("hqList", null);
                    List<Double> pList = map.getOrDefault("pList", null);
                    addByType(pList, pqMap, "pList");
                    addByType(hqList, pqMap, "qList");
                    addByType(cqList, pqMap, "qList");
                }
                calCopCH(copMap, pqMap, type.getTypeName().getName());
            }
        }
        return copMap;
    }


    @Override
    public Map<String, List<Double>> getCopCHAll(List<Date> timeList, Project project, String mode) {
        Map<String, List<List<Double>>> listMap = new HashMap<>();
        listMap.put("制冷量", new ArrayList<List<Double>>());
        listMap.put("制热量", new ArrayList<List<Double>>());
        listMap.put("能耗", new ArrayList<List<Double>>());
        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if (BusinessCollections.copTypeSet.contains(type.getTypeName().getName())) {
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    Map<String, List<Double>> pqMap = getQPMap(timeList, equipment, mode);
                    List<Double> cqList = pqMap.getOrDefault("cqList", null);
                    List<Double> hqList = pqMap.getOrDefault("hqList", null);
                    List<Double> pList = pqMap.getOrDefault("pList", null);
                    if (pList == null)
                        continue;
                    listMap.get("能耗").add(pList);
                    if (cqList != null)
                        listMap.get("制冷量").add(cqList);
                    if (hqList != null)
                        listMap.get("制热量").add(hqList);
                }
            }
        }
        Map<String, List<Double>> copAllList = calCopCHAll(listMap);
        Map<String, List<Double>> copMap = new HashMap<>();
        calCopCHAll("供冷", copMap, copAllList.get("制冷量"), copAllList.get("能耗"));
        calCopCHAll("供暖", copMap, copAllList.get("制热量"), copAllList.get("能耗"));
        return copMap;
    }

    @Override
    public Map<String, List<Double>> getPumpEff(List<Date> timeList, Project project, String mode) {
        Map<String, List<Double>> pumpNMap = new HashMap<>();
        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if (BusinessCollections.pumpTypeSet.contains(type.getTypeName().getName())) {
                Map<String, List<Double>> pumpMap = new HashMap<>();
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    Map<String, List<Double>> map = getPumpMap(timeList, equipment, mode);
                    List<Double> mList = map.getOrDefault("mList", null);
                    List<Double> poutList = map.getOrDefault("poutList", null);
                    List<Double> pinList = map.getOrDefault("pinList", null);
                    List<Double> nList = map.getOrDefault("nList", null);
                    addByType(mList, pumpMap, "mList");
                    addByType(poutList, pumpMap, "poutList");
                    addByType(pinList, pumpMap, "pinList");
                    addByType(nList, pumpMap, "nList");
                }
                calPumpN(pumpNMap, pumpMap, type.getTypeName().getName());
            }
        }
        return pumpNMap;
    }


    @Override
    public Double getPumpCoefficient(List<Date> timeList, Project project, String pumpType) {
        double q = 0.0;
        double p = 0.0;
        Double tempValue;
        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if (pumpType.equals(type.getTypeName().getName())) {
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    Optional<Device> cqDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%制冷量%");
                    Optional<Device> hqDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%制热量%");
                    Optional<Device> pDevice = deviceService.findByEquipmentAndDescriptionLike(equipment, "%能耗%");
                    if (!pDevice.isPresent())
                        continue;
                    tempValue = getDiffData(pDevice.get(), timeList.get(1), timeList.get(timeList.size() - 1));
                    p += tempValue.isNaN() ? 0 : tempValue;
                    if (cqDevice.isPresent()) {
                        tempValue = getDiffData(cqDevice.get(), timeList.get(1), timeList.get(timeList.size() - 1));
                        q += tempValue.isNaN() ? 0 : tempValue;
                    }
                    if (hqDevice.isPresent()) {
                        tempValue = getDiffData(hqDevice.get(), timeList.get(1), timeList.get(timeList.size() - 1));
                        q += tempValue.isNaN() ? 0 : tempValue;
                    }
                }
            }
        }
        return p > .000001 ? q / p : 0.0;
    }

    private double getDataByEquipmentDevice(String val, String deviceName, Equipment equipment, List<Date> timeList, Map<String, Object> map) {
        Optional<Device> turang = deviceService.findByEquipmentAndDescriptionLike(equipment, deviceName);
        if (turang.isPresent()) {
            Date end = timeList.get(timeList.size() - 1);
            Optional<DeviceDataStd> dataStdOptional = deviceDataStdService.findByDeviceAndCreateTime(turang.get(), end);
            dataStdOptional.ifPresent(device -> map.put(val, dataStdOptional.get().getData()));
        }
        if (!map.containsKey(val))
            map.put(val, 0);
        return Double.parseDouble(map.get(val).toString());
    }

    @Override
    public Map<String, Object> getHeatBalanceCoefficient(List<Date> timeList, Project project) {
        Map<String, Object> map = new HashMap<>();
        map.put("qf", 0);
        map.put("qx", 0);
        map.put("balance_rate", 0);
        map.put("change_heat", 0);
        map.put("turang", 0);

        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if (BusinessCollections.heatBalanceTypeSet.contains(type.getTypeName().getName())) {
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    getDataByEquipmentDevice("qf", "%地源侧制冷量%", equipment, timeList, map);
                    getDataByEquipmentDevice("qx", "%地源侧制热量%", equipment, timeList, map);
                    double balance_rate = Double.parseDouble(map.get("qf").toString()) > .000001 ? Double.parseDouble(map.get("qf").toString()) / Double.parseDouble(map.get("qx").toString()) : 0.0;
                    map.put("balance_rate", balance_rate);

                    double m = getDataByEquipmentDevice("m", "%总流量%", equipment, timeList, map);
                    double pout = getDataByEquipmentDevice("pout", "%出水温度%", equipment, timeList, map);
                    double pin = getDataByEquipmentDevice("pin", "%进水温度%", equipment, timeList, map);
                    map.put("change_heat", 0.0125 * m * Math.abs(pout - pin));
                }
            } else if ("测温井".equals(type.getTypeName().getName())) {
                List<Equipment> equipmentList = type.getEquipments();
                if (equipmentList.size() == 0)
                    continue;
                Equipment equipment = equipmentList.get(0);
                getDataByEquipmentDevice("turang", "%土壤温度%", equipment, timeList, map);
            }
        }
        return map;
    }

    @Override
    public Map<String, List<Double>> getBalanceTemp(List<Date> timeList, Project project, String mode) {
        Map<String, List<Double>> tempMap = new HashMap<>();
        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if ("测温井".equals(type.getTypeName().getName())) {
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    Map<String, List<Double>> pumpMap = getBalanceTempMap(timeList, equipment, mode);
                    for (Map.Entry<String, List<Double>> entry : pumpMap.entrySet())
                        tempMap.put(equipment.getName(), entry.getValue());
                }
            }
        }
        return tempMap;
    }


    @Override
    public Map<String, List<Double>> getBuriedPipeLine(List<Date> timeList, Project project, String mode) {
        Map<String, List<Double>> buriedPipeCaledMap = new HashMap<>();
        List<EquipmentTypeInProject> typeList = equipmentTypeInProjectService.findByProject(project);
        for (EquipmentTypeInProject type : typeList) {
            if (BusinessCollections.heatBalanceTypeSet.contains(type.getTypeName().getName())) {
                List<Equipment> equipmentList = type.getEquipments();
                for (Equipment equipment : equipmentList) {
                    Map<String, List<Double>> pumpMap = getBuriedPipeValueMap(timeList, equipment, mode);
                    calBuriedPipe(buriedPipeCaledMap, pumpMap, equipment);
                }
            }
        }
        return buriedPipeCaledMap;
    }

    /**
     * 功能描述:
     * <逐天统计devices中的采集点在某天之内的累计值
     * <例如 dates = [20210705000000,20210706000000,20210707000000,20210708050608当前时刻]  返回devices在20210705、20210706、20210707的采集值，以及20210708到目前为止的采集值
     * <当天采集值由当前最开始采集到的数据以及第二天最开始采集到的数据做差得到
     *
     * @param devices
     * @param dates
     * @param timeMode
     * @return
     */
    @Override
    public List<Double> getDataByDevicesAndDates(List<Device> devices, List<Date> dates, int timeMode) {
        List<DeviceDataStd> deviceDataStds = deviceDataStdService.findAllByDeviceInAndCreateTimeBetweenOrderByCreateTimeASC(devices, timeMode, dates.get(0), dates.get(dates.size() - 1));

        if (deviceDataStds.size() < 2) {
            throw new SsnyException(ResultCodeEnum.ERROR_DATA_NOENOUGH_EXCEPTION);
        }
        /**
         * 1.将某一个时刻的所有设备的数据累加起来
         * 2.两个时刻的累加值相减就是该时段的值
         */
        List<Double> datas = new ArrayList<>();
        double preData = 0;
        double currData = 0;
        //用来表示是不是第一次两个数据的createTime不同，第一次出现不同的时候是不需要做差的
        boolean flag = true;
        for (int i = 0; i < deviceDataStds.size(); i++) {
            if (i == 0) {
                currData += deviceDataStds.get(i).getData();
            } else {
                if (DateUtils.isSameTime(deviceDataStds.get(i - 1).getCreateTime(), deviceDataStds.get(i).getCreateTime())) {
                    currData += deviceDataStds.get(i).getData();
                } else {
                    if (flag) {//第一次出现两个数据创建时间不同
                        flag = false;
                    } else {//非第一次出现两个数据创建时间不同
                        datas.add(currData - preData);
                    }
                    preData = currData;
                    currData = deviceDataStds.get(i).getData();
                }
                if (i == deviceDataStds.size() - 1) {
                    datas.add(currData - preData);
                }
            }
        }
        return datas;
    }

    @Override
    public List<Double> getDataByDeviceIdsAndDates(List<Integer> deviceIds, List<Date> dates, int timeMode) {
        List<DeviceDataStd> deviceDataStds = deviceDataStdService.findAllByDeviceIdsInAndCreateTimeBetweenOrderByCreateTimeASC(deviceIds, timeMode, dates.get(0), dates.get(dates.size() - 1));

        if (deviceDataStds.size() < 2) {
            throw new SsnyException(ResultCodeEnum.ERROR_DATA_NOENOUGH_EXCEPTION);
        }
        /**
         * 1.将某一个时刻的所有设备的数据累加起来
         * 2.两个时刻的累加值相减就是该时段的值
         */
        List<Double> datas = new ArrayList<>();
        double preData = 0;
        double currData = 0;
        //用来表示是不是第一次两个数据的createTime不同，第一次出现不同的时候是不需要做差的
        boolean flag = true;
        for (int i = 0; i < deviceDataStds.size(); i++) {
            if (i == 0) {
                currData += deviceDataStds.get(i).getData();
            } else {
                if (DateUtils.isSameTime(deviceDataStds.get(i - 1).getCreateTime(), deviceDataStds.get(i).getCreateTime())) {
                    currData += deviceDataStds.get(i).getData();
                } else {
                    if (flag) {//第一次出现两个数据创建时间不同
                        flag = false;
                    } else {//非第一次出现两个数据创建时间不同
                        datas.add(currData - preData);
                    }
                    preData = currData;
                    currData = deviceDataStds.get(i).getData();
                }
                if (i == deviceDataStds.size() - 1) {
                    datas.add(currData - preData);
                }
            }
        }
        return datas;
    }


    /*时间是递增的*/ /*获取按照 时间节点 历史依赖的累积值*/
    public Map<Date, Double> getTotalDataByDevicesAndDates(List<Device> devices, List<Date> dates, int mode) {
//        Map<Date, Double> map = new HashMap<>();
//
//        //获取时间段开始
//        Date startDate = dates.get(0);
//        //获取时间段结束
//        Date endDate = getEndDate(dates, mode, 0);
////
//        List<DeviceDataStd> deviceDataStds = deviceDataStdService.findAllByDeviceInAndCreateTimeBetweenOrderByCreateTimeASC(devices, mode, startDate, endDate);
//        int days_index = 0;
//        Map<Integer, DeviceDataStd> currData = new HashMap<>();
//        Date preDate = null;
//        Date currDate = null;
//        for (int i = 0; i < deviceDataStds.size(); i++) {
//            if (currData.size() == devices.size() || i == deviceDataStds.size() - 1) {
//                if (i == deviceDataStds.size() - 1) {
//                    currData.put(deviceDataStds.get(i).getDevice().getId(), deviceDataStds.get(i));
//                    currDate = deviceDataStds.get(i).getCreateTime();
//                }
//                for (Device device : devices) {
//                    //避免有些时间段的数据是缺失的
//                    //最后一次的数据可能不是一个整的，所以需要直接处理，不用按照时间段缺失的方式处理
//                    // 如果中间有时间缺失 就用前一个时间点的数据填上，不能直接是0 因为是累积值
//                    while (days_index < dates.size() && DateUtils.compareDate(currDate, dates.get(days_index)) > 0) {
//                        if (days_index == 0) {
//                            map.put(dates.get(days_index), 0d);
//                        } else {
//                            map.put(dates.get(days_index), map.get(dates.get(days_index - 1)));
//                        }
//                        preDate = dates.get(days_index);
//                        days_index++;
//                    }
//                    if (days_index < dates.size() && currData.keySet().contains(device.getId())) {
//                        map.put(dates.get(days_index), map.getOrDefault(dates.get(days_index), 0d) + currData.get(device.getId()).getData());
//                        preDate = dates.get(days_index);
//                    }
//                }
//                currData.clear();
//                days_index++;
//            }
//
//            // 存储每一个设备每天开始时的数据值 需要存储每一个的 必须保证存满，并且对于已有的就不用存了
//            if ((preDate == null || (!DateUtils.isSameYear(preDate, deviceDataStds.get(i).getCreateTime()) && mode == DateUtils.MODE_YEAR ||
//                    !DateUtils.isSameMonth(preDate, deviceDataStds.get(i).getCreateTime()) && mode == DateUtils.MODE_MONTH ||
//                    !DateUtils.isSameDay(preDate, deviceDataStds.get(i).getCreateTime()) && mode == DateUtils.MODE_DAY ||
//                    !DateUtils.isSameHour(preDate, deviceDataStds.get(i).getCreateTime()) && mode == DateUtils.MODE_HOUR)) &&
//                    currData.size() < devices.size() && !currData.keySet().contains(deviceDataStds.get(i).getDevice().getId())) {
//                currData.put(deviceDataStds.get(i).getDevice().getId(), deviceDataStds.get(i));
//                currDate = deviceDataStds.get(i).getCreateTime();
//            }
//        }
//        return map;
        return null;
    }


    /**
     * 功能描述:
     * <指定项目，根据设备类型，返回每个类型设备的能耗(对于指定项目)
     *
     * @param project
     * @param equipmentTypes
     * @return
     */
    @Override
    public List<Double> getPowerCostByEquipmentTypeAndProject(Project project, List<String> equipmentTypes) {
        Map<String, Double> map = new HashMap<>();
        List<Equipment> equipments = equipmentService.findByProject(project);
        List<Equipment> targetEquipments = findEquipmentsByConllection(equipments, equipmentTypes);
        List<Device> targetDevices = deviceService.findByEquipmentInAndDescriptionLike(targetEquipments, "%能耗%");
        List<DeviceDataStd> dataStds = new ArrayList<>();
        if (targetDevices != null && targetDevices.size() != 0) {
            List<Integer> deviceIds = new ArrayList<>();
            targetDevices.forEach(device -> {
                deviceIds.add(device.getId());
            });
            dataStds = deviceDataStdDao.findAllByDeviceInAndCreateTimeDescAndLimitOnePerDevice(deviceIds);
        }
        for (int i = 0; i < dataStds.size(); i++) {
            String typeName = dataStds.get(i).getDevice().getEquipment().getType().getTypeName().getName();
            map.put(typeName, map.getOrDefault(typeName, 0d) + dataStds.get(i).getData());
        }
        List<Double> powerCosts = new ArrayList<>();
        for (String equipmentType : equipmentTypes) {
            powerCosts.add(map.getOrDefault(equipmentType, 0d));
        }
        return powerCosts;
    }

    /**
     * 功能描述：
     * <按照指定的设备类型分组，获取每个分组的能耗(对于所有项目)
     * <已经测试跑通，但是尚未测试数据
     *
     * @param equipmentTypes
     * @return
     */
    @Override
    public List<Double> getPowerCostByEquipmentTypes(List<String> equipmentTypes) {
        Map<String, Double> map = new HashMap<>();
        List<Equipment> equipments = equipmentService.findAll();
        List<Equipment> targetEquipments = findEquipmentsByConllection(equipments, equipmentTypes);
        List<Device> targetDevices = deviceService.findByEquipmentInAndDescriptionLike(targetEquipments, "%能耗%");
        List<DeviceDataStd> dataStds = new ArrayList<>();
        if (targetDevices != null && targetDevices.size() != 0) {
            List<Integer> deviceIds = new ArrayList<>();
            targetDevices.forEach(device -> {
                deviceIds.add(device.getId());
            });
            dataStds = deviceDataStdDao.findAllByDeviceInAndCreateTimeDescAndLimitOnePerDevice(deviceIds);
        }
        for (int i = 0; i < dataStds.size(); i++) {
            String typeName = dataStds.get(i).getDevice().getEquipment().getType().getTypeName().getName();
            map.put(typeName, map.getOrDefault(typeName, 0d) + dataStds.get(i).getData());
        }
        List<Double> powerCosts = new ArrayList<>();
        for (String equipmentType : equipmentTypes) {
            powerCosts.add(map.getOrDefault(equipmentType, 0d));
        }
        return powerCosts;
    }


    /**
     * 功能描述:
     * <获取指定设备集合的能耗统计，主要用于制冷模式或者供暖模式的能耗统计（不区分项目）
     * <并且按照日期进行返回
     *
     * @param dates
     * @param equipmentTypes
     * @return
     */
    @Override
    public List<Double> getPowerCostByDaysAndEquipmentTypes(List<Date> dates, List<String> equipmentTypes) {
        //获取所有的设备
        List<Equipment> equipments = equipmentService.findAll();
        //获取需要被统计的设备
        List<Equipment> targetEquipments = findEquipmentsByConllection(equipments, equipmentTypes);
        //获取需要被统计的采集点
        List<Device> targetDevices = deviceService.findByEquipmentInAndDescriptionLike(targetEquipments, "%能耗%");
        List<Double> powerCosts = deviceDataStdService.getDataByDevicesAndDates(targetDevices, dates, DateUtils.MODE_DAY);
        return powerCosts;
    }

    /**
     * 功能描述:
     * <计算机组能效-指定项目
     * <eneryEquipment的制冷或者供暖量除以powercostEquipements的能耗总和(逐天计算，指定项目)
     *
     * @param project
     * @param dates
     * @param powercostEquipementTypes
     * @param energyEquipmentTypes
     * @param mode
     * @param timeMode
     * @return
     */
    @Override
    public List<Double> getCOPByProject(Project project, List<Date> dates, List<String> powercostEquipementTypes, List<String> energyEquipmentTypes, int mode, int timeMode) {
        List<Equipment> equipments = equipmentService.findByProject(project);
        // 制冷量或者供暖量统计设备
        List<Equipment> energyEquipments = findEquipmentsByConllection(equipments, energyEquipmentTypes);
        // 能耗统计设备
        List<Equipment> powerCostEquipments = findEquipmentsByConllection(equipments, powercostEquipementTypes);
        String desc = (mode == MODE_COLD) ? "%制冷量%" : "%制热量%";
        // 查询出供冷量或供暖量采集点
        List<Device> energyDevices = deviceService.findByEquipmentInAndDescriptionLike(energyEquipments, desc);
        // 查询出能耗采集点
        List<Device> powerCostDevices = deviceService.findByEquipmentInAndDescriptionLike(powerCostEquipments, "%能耗%");

        List<Double> powerCosts = deviceDataStdService.getDataByDevicesAndDates(powerCostDevices, dates, timeMode);
        List<Double> energies = deviceDataStdService.getDataByDevicesAndDates(energyDevices, dates, timeMode);

        List<Double> cop = new ArrayList<>();
        for (int i = 0; i < dates.size(); i++) {
            if (powerCosts.get(i) == 0) {
                cop.add(energies.get(i) / powerCosts.get(i));
            } else {
                cop.add(0d);
            }
        }
        return cop;
    }

    /**
     * 功能描述:
     * <计算系统能效-指定项目
     * <eneryEquipment的制冷或者供暖量除以powercostEquipements的能耗总和(逐天计算，指定项目)
     * <功能等同于函数getCOPByProject
     *
     * @param project
     * @param dates
     * @param powercostEquipementTypes
     * @param energyEquipmentTypes
     * @param mode
     * @return
     */
    @Override
    public List<Double> getSCOPByProject(Project project, List<Date> dates, List<String> powercostEquipementTypes, List<String> energyEquipmentTypes, int mode, int timeMode) {
        return getCOPByProject(project, dates, powercostEquipementTypes, energyEquipmentTypes, mode, timeMode);
    }


    /**
     * 功能描述：
     * <eneryEquipment的制冷或者供暖量除以powercostEquipements的能耗总和(逐天计算)
     *
     * @param dates
     * @param powercostEquipementTypes 统计能耗的设备集合
     * @param energyEquipmentTypes     统计制冷或者供暖的能耗集合
     * @param timeMode
     * @return
     */
    @Override
    public List<Double> getSCOP(List<Date> dates, List<String> powercostEquipementTypes, List<String> energyEquipmentTypes, int mode, int timeMode) {
        //逐天统计，得到每一天的能耗
        List<Double> powerCosts = deviceDataStdService.getPowerCostByDaysAndEquipmentTypes(dates, powercostEquipementTypes);

        //逐天统计，得到每一天的制冷量或者供暖量
        List<Equipment> equipments = equipmentService.findAll();
        List<Equipment> targetEquipment = findEquipmentsByConllection(equipments, energyEquipmentTypes);
        String desc = mode == Constants.HeatingMode ? "%制热量%" : "%制冷量%";
        List<Device> targetDevices = deviceService.findByEquipmentInAndDescriptionLike(targetEquipment, desc);
        List<Double> energies = deviceDataStdService.getDataByDevicesAndDates(targetDevices, dates, timeMode);
        List<Double> scops = new ArrayList<>(dates.size());
        for (int i = 0; i < dates.size(); i++) {
            if (powerCosts.get(i) != 0) {
                scops.add(energies.get(i) / powerCosts.get(i));
            } else {
                scops.add(0.0);
            }
        }
        return scops;
    }


    private Double renewableEnery = null;

    /*已测试*/
    /*可再生能源利用量 = 地源热泵机组累计制冷量+地源热泵机组累计制热量 kj*/
    @Override
    public Double getRenewableEnery() {
        List<Equipment> equipments = equipmentService.findAll();
        //获得需要被统计的设备
        List<Equipment> targetEquipments = findEquipmentsByConllection(equipments, BusinessCollections.renewableEneryTypeSet);

        //制冷采集点
        List<Device> coldDevices = deviceService.findByEquipmentInAndDescriptionLike(targetEquipments, "%地源侧制冷量%");
        //制热采集点
        List<Device> hotDevices = deviceService.findByEquipmentInAndDescriptionLike(targetEquipments, "%地源侧制热量%");

        List<Date> dates = new ArrayList<>();
        Date today = DateUtils.today();
        Date nextDay = DateUtils.addDays(today, 1);
        dates.add(today);
        dates.add(nextDay);
        Map<Date, Double> coldData = deviceDataStdService.getTotalDataByDevicesAndDates(coldDevices, dates, DateUtils.MODE_DAY);
        Map<Date, Double> hotData = deviceDataStdService.getTotalDataByDevicesAndDates(hotDevices, dates, DateUtils.MODE_DAY);
        renewableEnery = coldData.getOrDefault(nextDay, 0d) * 1.333 + hotData.getOrDefault(nextDay, 0d) * 0.818;
        return renewableEnery;
    }

    /*节约标煤量  0.326(kgce/kWh)*可再生能源利用量（kWh）,1kJ=0.000278kWh。*/
    @Override
    public Double getSaveCoal() {
        if (renewableEnery == null) {
            renewableEnery = getRenewableEnery();
        }
        return renewableEnery * 0.000278 * 0.326;
    }

    /*碳减排量 0.795(kg/kWh)*可再生能源利用量（kWh）1kJ=0.000278kWh*/
    @Override
    public Double getCarbinReduction() {
        if (renewableEnery == null) {
            renewableEnery = getRenewableEnery();
        }
        return renewableEnery * 0.000278 * 0.795;
    }

    /**
     * 功能描述:
     * <根据设备type在equipments中找出所有满足条件的设备
     *
     * @param equipments
     * @param equipmentTypes
     * @return
     */
    private List<Equipment> findEquipmentsByConllection(List<Equipment> equipments, Collection<String> equipmentTypes) {
        List<Equipment> targetEquipments = new ArrayList<>();
        for (int i = 0; i < equipments.size(); i++) {
            Equipment equipment = equipments.get(i);
            Optional.ofNullable(equipment.getType()).ifPresent(type -> {
                Optional.ofNullable(type.getTypeName()).ifPresent(name -> {
                    Optional.ofNullable(name.getName()).ifPresent(typeName -> {
                        if (equipmentTypes.contains(typeName)) {
                            targetEquipments.add(equipment);
                        }
                    });
                });
            });
        }
        return targetEquipments;
    }

}